//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License
// along with this program.  If not, see http://www.gnu.org/licenses/.
// 

#include "Emulate3G.h"

#define STAT_STEP   0.5

Define_Module(Emulate3G);

Emulate3G::Emulate3G() : rs(this->getId()) {
    // TODO Auto-generated constructor stub
}

Emulate3G::~Emulate3G() {
    std::list<cPacket *>::iterator it_queue;
    it_queue = pkyQueue.begin();
    while(it_queue != pkyQueue.end()) {
        cPacket *p = *it_queue;

        delete(p);

        it_queue = pkyQueue.erase(it_queue);
    }
    cancelAndDelete(msgEndPkt);

    delete (byteSent);
    delete (pktSent);

    cancelAndDelete(msgStat);

    /*{
        FILE *fDebug = fopen("distructordebug.txt", "a");
        if(fDebug != NULL) {
            char buff[64];
            int nChar = snprintf(buff, sizeof(buff), "Emulate3G. Destructor end\r\n");
            fwrite(buff, 1, nChar, fDebug); fclose(fDebug);
        }
    }*/
}

void Emulate3G::initialize(int stage) {
    if(stage == 0) {

        nb = NotificationBoardAccess().get();

        brainIn = findGate("dataIn");
        brainOut = findGate("dataOut");

        maxQueueSize = par("maxQueueSize");
        usage_3g_radio = par("usage_3g_radio");
        cellularCapacity = par("cellularCapacity");

        rs.setRadioId(this->getId());
        rs.setBitrate(cellularCapacity);

        batt = check_and_cast<BasicBattery *>(this->getParentModule()->getParentModule()->getSubmodule("battery"));

        cellularCapacity /= 8; // changing from bps to B/s

        msgEndPkt = new cMessage("msgEndPkt");
        msgStat = new cMessage("msgStat");
        scheduleAt(simTime() + STAT_STEP, msgStat);

        pktSent =  new cOutVector("Packets sent by 3G");
        byteSent = new cOutVector("Byte sent by 3G");

        droppedPkt = 0;
        countPktSent = 0;
        totalPktSent = 0;

        energyToDraw = DrawAmount(DrawAmount::CURRENT, usage_3g_radio);

        myState = OFF_3G;

        pktSentList.clear();

        WATCH(rs);
    }
    else if (stage == 1) {
        //myBatteryID = batt->registerDevice(this, 1);

        registerBattery();

        rs.setState(RadioState::SLEEP);
        nb->fireChangeNotification(NF_RADIOSTATE_CHANGED, &rs);
    }
}

void Emulate3G::registerBattery(void) {
    if (batt)
    {
        //int id,double mUsageRadioIdle,double mUsageRadioRecv,double mUsageRadioSend,double mUsageRadioSleep)=0;
        // read parameters
        double mUsageRadioIdle = par("usage_radio_idle");
        double mUsageRadioRecv = par("usage_radio_recv");
        double mUsageRadioSleep = par("usage_radio_sleep");
        double mUsageRadioSend = par("usage_radio_send");
        if (mUsageRadioIdle<0 || mUsageRadioRecv<0 || mUsageRadioSleep<0 || mUsageRadioSend < 0)
            return;
        batt->registerWirelessDevice(rs.getRadioId(), mUsageRadioIdle, mUsageRadioRecv, mUsageRadioSend, mUsageRadioSleep);
    }
}

void Emulate3G::handleMessage(cMessage *msg) {
    if (msg->isSelfMessage()) {
        handleSelfMsg(msg);
    }
    else if (msg->getArrivalGateId() == brainIn) {
        handleBrainMsg(msg);
    }
    else {
        error("Unknown message received: %s", msg->getFullName());
        delete(msg);
    }
}

void Emulate3G::handleSelfMsg(cMessage *msg) {
    if(msg == msgEndPkt) {
        if(myState == OFF_3G) {
            return;
        }

        if(pkyQueue.size() == 0) {
            myState = IDLE_3G;

            rs.setState(RadioState::IDLE);
            nb->fireChangeNotification(NF_RADIOSTATE_CHANGED, &rs);
        }
        else {
            // there are other packets in the queue, managing them now...
            double time_to_send;

            cPacket *p = pkyQueue.front();
            pkyQueue.pop_front();

            countPktSent++;
            totalPktSent++;

            //byteSent->record(p->getByteLength());

            time_to_send = ((double) p->getByteLength()) / cellularCapacity;
            scheduleAt(simTime() + time_to_send, msgEndPkt);

            //batt->draw(myBatteryID, energyToDraw, 0);

            if (rs.getState() != RadioState::TRANSMIT) {
                rs.setState(RadioState::TRANSMIT);
                nb->fireChangeNotification(NF_RADIOSTATE_CHANGED, &rs);
            }

            delete(p);
        }
    }
    else if (msg == msgStat) {
        makePeriodocStats();
        scheduleAt(simTime() + STAT_STEP, msgStat);
    }
    else {
        error("Unknown self message received: %s", msg->getFullName());
        delete(msg);
    }
}


void Emulate3G::handleBrainMsg(cMessage *msg) {
    cPacket *pkt = check_and_cast<cPacket *>(msg);
    double time_to_send;

    EV << "Received a packet from the brain. My state is: " << myState << endl;
    EV << "My actual 3G queue is: " << getQueueSize() << endl;

    switch(myState) {
    case OFF_3G:

        // drop the message (I'm powered off)
        delete(msg);

        break;

    case IDLE_3G:
        // i'm idle, i can do it now
        time_to_send = ((double) pkt->getByteLength()) / cellularCapacity;

        countPktSent++;
        totalPktSent++;
        //byteSent->record(pkt->getByteLength());

        if (msgEndPkt->isScheduled()){
            cancelEvent(msgEndPkt);
        }
        scheduleAt(simTime() + time_to_send, msgEndPkt);

        //batt->draw(myBatteryID, energyToDraw, 0);
        myState = BUSY_3G;

        rs.setState(RadioState::TRANSMIT);
        nb->fireChangeNotification(NF_RADIOSTATE_CHANGED, &rs);

        delete(msg);

        break;

    case BUSY_3G:
        // i'm busy, i can do it later (may be...)
        if(((int) pkyQueue.size()) < maxQueueSize) {
            pkyQueue.push_back(pkt);
        }
        else {
            delete(msg);

            droppedPkt++;
        }
        break;
    }
}

void Emulate3G::finish() {
    makeFinalStats();
}

double Emulate3G::getQueueSize(void) {
    return pkyQueue.size();
}

double Emulate3G::getMaxQueueSize(void) {
    return maxQueueSize;
}

void Emulate3G::switchON(void) {
    //return;
    if (myState == OFF_3G) {
        myState = IDLE_3G;
        //cancelEvent(msgEndPkt);

        rs.setState(RadioState::IDLE);
        nb->fireChangeNotification(NF_RADIOSTATE_CHANGED, &rs);
    }
}

void Emulate3G::switchOFF(void) {
    //return;
    if (myState != OFF_3G) {
        myState = OFF_3G;

        rs.setState(RadioState::SLEEP);
        nb->fireChangeNotification(NF_RADIOSTATE_CHANGED, &rs);

        //cancelEvent(msgEndPkt);

        // empty the queue
        std::list<cPacket *>::iterator it_queue = pkyQueue.begin();
        while(it_queue != pkyQueue.end()) {
            cPacket *p = *it_queue;
            delete(p);

            it_queue = pkyQueue.erase(it_queue);
        }
    }
}

//stats
void Emulate3G::makePeriodocStats(void) {
    pktSent->record(countPktSent);
    pktSentList.push_back(std::make_pair<simtime_t, int>(simTime(), countPktSent));
    countPktSent = 0;
}

void Emulate3G::makeFinalStats(void) {

    simtime_t nowT = simTime();
    /*int countPkt = 0;
    int sumPkt = 0;
    int totPkt = 0;


    for (std::list<std::pair<simtime_t, int> >::iterator it = pktSentList.begin(); it != pktSentList.end(); it++) {
        if(it->first >= (nowT - FINAL_STAT_MEAN_PERIOD)) {
            countPkt++;
            sumPkt += it->second;
        }
    }

    if(countPkt > 0) {
        totPkt = sumPkt / countPkt;
    }
    recordScalar("Average Final Packets Sent", totPkt);*/
    recordScalar("Average Final Packets Sent AllTime", getAveragePktSentPerSeconds(nowT));
    recordScalar("Average Final Packets Sent HalfTime", getAveragePktSentPerSeconds(nowT/2.0));
    recordScalar("Average Final Packets Sent", getAveragePktSentPerSeconds(FINAL_STAT_MEAN_PERIOD));

    if(this->getParentModule()->getParentModule()->getIndex() == 0) {
        int nNodes = this->getParentModule()->getParentModule()->getParentModule()->par("numHosts");
        double sumPktAverage = 0;
        double sumPktAverageAll = 0;
        double sumPktAverageHalf = 0;

        for (int i = 0; i < nNodes; i++) {
            Emulate3G *e3g = check_and_cast<Emulate3G *>(this->getParentModule()->getParentModule()->getParentModule()->getSubmodule("host", i)->getSubmodule("udpApp", 0)->getSubmodule("emulate3G"));

            sumPktAverage += e3g->getAveragePktSentPerSeconds(FINAL_STAT_MEAN_PERIOD);
            sumPktAverageAll += e3g->getAveragePktSentPerSeconds(nowT);
            sumPktAverageHalf += e3g->getAveragePktSentPerSeconds(nowT/2.0);
        }

        recordScalar("Average Total Final Packets Sent", sumPktAverage);
        recordScalar("Average Total Final Packets Sent AllTime", sumPktAverageAll);
        recordScalar("Average Total Final Packets Sent HalfTime", sumPktAverageHalf);
    }

    recordScalar("Total Packets Sent", totalPktSent);

}

double Emulate3G::getAveragePktSentPerSeconds(simtime_t time) {
    int countPkt = 0;
    double sumPkt = 0;
    double totPkt = 0;
    simtime_t nowT = simTime();

    for (std::list<std::pair<simtime_t, int> >::iterator it = pktSentList.begin(); it != pktSentList.end(); it++) {
        //if((total) || (it->first >= (nowT - FINAL_STAT_MEAN_PERIOD)) ) {
        if(it->first >= (nowT - time) ) {
            countPkt++;
            sumPkt += it->second;
        }
    }

    if(countPkt > 0) {
        totPkt = sumPkt / (double)countPkt;
        totPkt = totPkt / STAT_STEP;
    }

    return totPkt;
}
